// 版权所有2009 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// Package exec运行外部命令。它包装了os。启动程序，使重新映射stdin和stdout、将I/O与管道连接以及执行其他
// 调整变得更容易。
// 
// 与C和其他语言的“系统”库调用不同，
// os/exec包故意不调用系统shell，
// 不扩展任何全局模式或处理其他扩展、
// 管道或通常由shell执行的重定向。包
// 的行为更像C的“exec”函数族。要扩展glob 
// 模式，可以直接调用shell，注意避开任何危险的输入，或者使用path/filepath包的glob函数。
// 要扩展环境变量，请使用软件包os的ExpandEnv。
// 
// 请注意，本软件包中的示例假定为Unix系统。
// 它们不能在Windows上运行，也不能在golang使用的围棋场上运行。org和godoc。组织。
package exec

import (
	"bytes"
	"context"
	"errors"
	"internal/syscall/execenv"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"syscall"
)

// 当LookPath无法将文件分类为
// 可执行文件时，它将返回错误。
type Error struct {
	// Name是发生错误的文件名。
	Name string
	// Err是基本错误。
	Err error
}

func (e *Error) Error() string {
	return "exec: " + strconv.Quote(e.Name) + ": " + e.Err.Error()
}

func (e *Error) Unwrap() error { return e.Err }

// Cmd表示正在准备或运行的外部命令。
// 
// Cmd在调用其Run、Output或combined Output 
// 方法后无法重用。
type Cmd struct {
	// Path是要运行的命令的路径。
	// 
	// 这是唯一必须设置为非零
	// 值的字段。如果路径是相对的，那么它的计算是相对于目录的
	// 。
	Path string

	// Args保存命令行参数，包括作为Args[0]的命令。
	// 如果Args字段为空或为零，Run将使用{Path}。
	// 
	// 在典型使用中，路径和参数都是通过调用命令来设置的。
	Args []string

	// Env指定进程的环境。
	// 每个条目的格式为“key=value”。
	// 如果Env为nil，新进程将使用当前进程的
	// 环境。
	// 如果Env包含重复的环境键，则仅使用每个重复键所在切片中的最后一个
	// 值。
	// 作为Windows上的一个特例，如果缺少
	// 且未显式设置为空字符串，则始终添加SYSTEMROOT。
	Env []string

	// Dir指定命令的工作目录。
	// 如果Dir是空字符串，Run在
	// 调用进程的当前目录中运行命令。
	Dir string

	// Stdin指定进程的标准输入。
	// 
	// 如果Stdin为nil，则进程从空设备（os.DevNull）读取数据。
	// 
	// 如果Stdin是*os。文件，进程的标准输入直接连接到该文件。
	// 
	// 否则，在命令执行过程中，一个单独的
	// goroutine从Stdin读取数据，并通过管道将数据传递给命令
	// 。在这种情况下，等待直到goroutine 
	// 停止复制才完成，这可能是因为它已到达Stdin 
	// （EOF或读取错误）的末尾，也可能是因为写入管道返回了错误。
	Stdin io.Reader

	// Stdout和Stderr指定进程的标准输出和错误。
	// 
	// 如果其中一个为零，Run将相应的文件描述符
	// 连接到空设备（os.DevNull）。
	// 
	// 如果其中一个是*os。文件，进程
	// 的相应输出直接连接到该文件。
	// 
	// 否则，在执行命令期间，一个单独的goroutine 
	// 通过管道从进程中读取数据，并将该数据传递给
	// 相应的编写器。在这种情况下，等待直到
	// goroutine达到EOF或遇到错误才完成。
	// 
	// 如果Stdout和Stderr是同一个writer，并且有一个可以与==进行比较的类型，那么一次最多一个goroutine将调用Write。
	Stdout io.Writer
	Stderr io.Writer

	// ExtraFiles指定新进程要继承的其他打开的文件。它不包括标准输入、标准输出或
	// 标准错误。如果非nil，条目i将成为文件描述符3+i。Windows不支持外部文件。
	ExtraFiles []*os.File

	// SysProcAttr持有可选的、特定于操作系统的属性。
	// Run将其传递给操作系统。作为操作系统启动流程。ProcAttr的系统字段。
	SysProcAttr *syscall.SysProcAttr

	// 流程是基础流程，一旦启动。
	Process *os.Process

	// ProcessState包含有关已退出进程的信息，
	// 在调用等待或运行后可用。
	ProcessState *os.ProcessState

	ctx             context.Context // nil表示无
	lookPathErr     error           // LookPath错误（如果有）。
	finished        bool            // 调用Wait时
	childFiles      []*os.File
	closeAfterStart []io.Closer
	closeAfterWait  []io.Closer
	goroutine       []func() error
	errch           chan error // 每个goroutine发送一次
	waitDone        chan struct{}
}

// 命令返回Cmd struct以使用给定参数
// 执行命名程序。
// 
// 它只设置返回结构中的路径和参数。
// 
// 如果名称不包含路径分隔符，则命令将使用LookPath来
// 如果可能，将名称解析为完整路径。否则它直接使用名称
// 作为路径。
// 
// 返回的Cmd的Args字段是由命令名
// 后跟arg元素构成的，因此arg不应包括
// 命令名本身。例如，Command（“echo”、“hello”）。
// Args[0]始终是名称，而不是可能解析的路径。
// 
// 在Windows上，进程以单个字符串的形式接收整个命令行，并自行进行解析。Command将Args组合并引用到命令
// 行字符串中，其算法与使用
// CommandLineToArgvW的应用程序兼容（这是最常见的方法）。值得注意的例外是
// msiexec。exe和cmd。exe（以及所有批处理文件），它们具有不同的
// 取消引用算法。在这些或其他类似的情况下，您可以自己引用
// 并在sysprocatr中提供完整的命令行。CmdLine，
// 将Args留空。
func Command(name string, arg ...string) *Cmd {
	cmd := &Cmd{
		Path: name,
		Args: append([]string{name}, arg...),
	}
	if filepath.Base(name) == name {
		if lp, err := LookPath(name); err != nil {
			cmd.lookPathErr = err
		} else {
			cmd.Path = lp
		}
	}
	return cmd
}

// CommandContext与Command类似，但包含一个上下文。
// 
// 如果上下文在命令
// os.process.kill）。
// 自行完成之前完成，则提供的上下文用于终止进程（通过调用
func CommandContext(ctx context.Context, name string, arg ...string) *Cmd {
	if ctx == nil {
		panic("nil Context")
	}
	cmd := Command(name, arg...)
	cmd.ctx = ctx
	return cmd
}

// String返回一个人类可读的c语言描述。
// 它仅用于调试。
// 尤其不适合用作shell的输入。
// 字符串的输出可能因Go版本而异。
func (c *Cmd) String() string {
	if c.lookPathErr != nil {
		// 解析路径失败；报告原始请求的路径（加上args）
		return strings.Join(c.Args, " ")
	}
	// 报告确切的可执行路径（加上args）
	b := new(strings.Builder)
	b.WriteString(c.Path)
	for _, a := range c.Args[1:] {
		b.WriteByte(' ')
		b.WriteString(a)
	}
	return b.String()
}

// interfaceEqual防止在
// 两个具有不可比较底层类型的接口上进行相等测试时出现恐慌。
func interfaceEqual(a, b any) bool {
	defer func() {
		recover()
	}()
	return a == b
}

func (c *Cmd) envv() ([]string, error) {
	if c.Env != nil {
		return c.Env, nil
	}
	return execenv.Default(c.SysProcAttr)
}

func (c *Cmd) argv() []string {
	if len(c.Args) > 0 {
		return c.Args
	}
	return []string{c.Path}
}

// SkipsdinCopyError可选地指定一个函数，该函数报告
// 是否应忽略提供的stdin复制错误。
var skipStdinCopyError func(error) bool

func (c *Cmd) stdin() (f *os.File, err error) {
	if c.Stdin == nil {
		f, err = os.Open(os.DevNull)
		if err != nil {
			return
		}
		c.closeAfterStart = append(c.closeAfterStart, f)
		return
	}

	if f, ok := c.Stdin.(*os.File); ok {
		return f, nil
	}

	pr, pw, err := os.Pipe()
	if err != nil {
		return
	}

	c.closeAfterStart = append(c.closeAfterStart, pr)
	c.closeAfterWait = append(c.closeAfterWait, pw)
	c.goroutine = append(c.goroutine, func() error {
		_, err := io.Copy(pw, c.Stdin)
		if skip := skipStdinCopyError; skip != nil && skip(err) {
			err = nil
		}
		if err1 := pw.Close(); err == nil {
			err = err1
		}
		return err
	})
	return pr, nil
}

func (c *Cmd) stdout() (f *os.File, err error) {
	return c.writerDescriptor(c.Stdout)
}

func (c *Cmd) stderr() (f *os.File, err error) {
	if c.Stderr != nil && interfaceEqual(c.Stderr, c.Stdout) {
		return c.childFiles[1], nil
	}
	return c.writerDescriptor(c.Stderr)
}

func (c *Cmd) writerDescriptor(w io.Writer) (f *os.File, err error) {
	if w == nil {
		f, err = os.OpenFile(os.DevNull, os.O_WRONLY, 0)
		if err != nil {
			return
		}
		c.closeAfterStart = append(c.closeAfterStart, f)
		return
	}

	if f, ok := w.(*os.File); ok {
		return f, nil
	}

	pr, pw, err := os.Pipe()
	if err != nil {
		return
	}

	c.closeAfterStart = append(c.closeAfterStart, pw)
	c.closeAfterWait = append(c.closeAfterWait, pr)
	c.goroutine = append(c.goroutine, func() error {
		_, err := io.Copy(w, pr)
		pr.Close() // 以防io。由于写入错误
		return err
	})
	return pw, nil
}

func (c *Cmd) closeDescriptors(closers []io.Closer) {
	for _, fd := range closers {
		fd.Close()
	}
}

// Run停止复制启动指定的命令并等待其完成。
// 
// 如果命令运行，返回的错误为零，没有问题
// 复制stdin、stdout和stderr，并以零退出
// 状态退出。
// 
// 如果命令启动但未成功完成，则错误为
// type*exitror。对于其他情况，可能会返回其他错误类型。
// 
// 如果调用的goroutine已在运行时锁定操作系统线程
// 。LockOSThread并修改了任何可继承的操作系统级
// 线程状态（例如，Linux或Plan 9命名空间），新的
// 进程将继承调用方的线程状态。
func (c *Cmd) Run() error {
	if err := c.Start(); err != nil {
		return err
	}
	return c.Wait()
}

// lookExtensions通过其目录和路径查找windows可执行文件。
// 它使用LookPath来尝试适当的扩展。
// lookExtensions不搜索路径，而是将'prog'转换为'。\prog`。
func lookExtensions(path, dir string) (string, error) {
	if filepath.Base(path) == path {
		path = filepath.Join(".", path)
	}
	if dir == "" {
		return LookPath(path)
	}
	if filepath.VolumeName(path) != "" {
		return LookPath(path)
	}
	if len(path) > 1 && os.IsPathSeparator(path[0]) {
		return LookPath(path)
	}
	dirandpath := filepath.Join(dir, path)
	// 我们假设LookPath只会添加文件扩展名。
	lp, err := LookPath(dirandpath)
	if err != nil {
		return "", err
	}
	ext := strings.TrimPrefix(lp, dirandpath)
	return path + ext, nil
}

// Start启动指定的命令，但不等待它完成。
// 
// 如果Start成功返回，将设置c.进程字段。
// 
// 一旦命令退出，Wait方法将返回退出代码并释放相关资源
// 。
func (c *Cmd) Start() error {
	if c.lookPathErr != nil {
		c.closeDescriptors(c.closeAfterStart)
		c.closeDescriptors(c.closeAfterWait)
		return c.lookPathErr
	}
	if runtime.GOOS == "windows" {
		lp, err := lookExtensions(c.Path, c.Dir)
		if err != nil {
			c.closeDescriptors(c.closeAfterStart)
			c.closeDescriptors(c.closeAfterWait)
			return err
		}
		c.Path = lp
	}
	if c.Process != nil {
		return errors.New("exec: already started")
	}
	if c.ctx != nil {
		select {
		case <-c.ctx.Done():
			c.closeDescriptors(c.closeAfterStart)
			c.closeDescriptors(c.closeAfterWait)
			return c.ctx.Err()
		default:
		}
	}

	c.childFiles = make([]*os.File, 0, 3+len(c.ExtraFiles))
	type F func(*Cmd) (*os.File, error)
	for _, setupFd := range []F{(*Cmd).stdin, (*Cmd).stdout, (*Cmd).stderr} {
		fd, err := setupFd(c)
		if err != nil {
			c.closeDescriptors(c.closeAfterStart)
			c.closeDescriptors(c.closeAfterWait)
			return err
		}
		c.childFiles = append(c.childFiles, fd)
	}
	c.childFiles = append(c.childFiles, c.ExtraFiles...)

	envv, err := c.envv()
	if err != nil {
		return err
	}

	c.Process, err = os.StartProcess(c.Path, c.argv(), &os.ProcAttr{
		Dir:   c.Dir,
		Files: c.childFiles,
		Env:   addCriticalEnv(dedupEnv(envv)),
		Sys:   c.SysProcAttr,
	})
	if err != nil {
		c.closeDescriptors(c.closeAfterStart)
		c.closeDescriptors(c.closeAfterWait)
		return err
	}

	c.closeDescriptors(c.closeAfterStart)

	// 除非有要触发的Goroutine，否则不要分配通道。
	if len(c.goroutine) > 0 {
		c.errch = make(chan error, len(c.goroutine))
		for _, fn := range c.goroutine {
			go func(fn func() error) {
				c.errch <- fn()
			}(fn)
		}
	}

	if c.ctx != nil {
		c.waitDone = make(chan struct{})
		go func() {
			select {
			case <-c.ctx.Done():
				c.Process.Kill()
			case <-c.waitDone:
			}
		}()
	}

	return nil
}

// ExitError通过命令报告退出失败。
type ExitError struct {
	*os.ProcessState

	// Stderr保存来自
	// Cmd的标准错误输出的子集。如果标准错误不是
	// 收集的，则输出方法。
	// 
	// 如果错误输出很长，Stderr可能只包含前缀
	// 和输出的后缀，中间部分替换为关于省略字节数的
	// 文本。
	// 
	// Stderr用于调试，用于包含在错误消息中。
	// 有其他需求的用户应重定向Cmd。根据需要进行测试。
	Stderr []byte
}

func (e *ExitError) Error() string {
	return e.ProcessState.String()
}

// Wait等待命令退出，并等待复制到
// stdin或从stdout或stderr复制完成。
// 
// 该命令必须是由Start启动的。
// 
// 如果命令运行，返回的错误为零，没有问题
// 复制stdin、stdout和stderr，并以零退出
// 状态退出。
// 
// 如果命令未能运行或未能成功完成，则
// 错误类型为*EXITORR。其他错误类型可能是
// 针对I/O问题返回的错误。
// 
// 如果c.Stdin、c.Stdout或c.Stderr中的任何一个不是*os。文件，Wait还将等待
// 以完成向进程或从进程复制的各个I/O循环。
// 
// Wait释放与Cmd相关的任何资源。
func (c *Cmd) Wait() error {
	if c.Process == nil {
		return errors.New("exec: not started")
	}
	if c.finished {
		return errors.New("exec: Wait was already called")
	}
	c.finished = true

	state, err := c.Process.Wait()
	if c.waitDone != nil {
		close(c.waitDone)
	}
	c.ProcessState = state

	var copyError error
	for range c.goroutine {
		if err := <-c.errch; err != nil && copyError == nil {
			copyError = err
		}
	}

	c.closeDescriptors(c.closeAfterWait)

	if err != nil {
		return err
	} else if !state.Success() {
		return &ExitError{ProcessState: state}
	}

	return copyError
}

// Output运行命令并返回其标准输出。
// 任何返回的错误通常都是*EXITORR类型。
// 如果c.Stderr为零，则输出填充exitorror。斯特德尔。
func (c *Cmd) Output() ([]byte, error) {
	if c.Stdout != nil {
		return nil, errors.New("exec: Stdout already set")
	}
	var stdout bytes.Buffer
	c.Stdout = &stdout

	captureErr := c.Stderr == nil
	if captureErr {
		c.Stderr = &prefixSuffixSaver{N: 32 << 10}
	}

	err := c.Run()
	if err != nil && captureErr {
		if ee, ok := err.(*ExitError); ok {
			ee.Stderr = c.Stderr.(*prefixSuffixSaver).Bytes()
		}
	}
	return stdout.Bytes(), err
}

// CombinedOutput运行命令并返回其组合的标准
// 输出和标准错误。
func (c *Cmd) CombinedOutput() ([]byte, error) {
	if c.Stdout != nil {
		return nil, errors.New("exec: Stdout already set")
	}
	if c.Stderr != nil {
		return nil, errors.New("exec: Stderr already set")
	}
	var b bytes.Buffer
	c.Stdout = &b
	c.Stderr = &b
	err := c.Run()
	return b.Bytes(), err
}

// StdinPipe返回一个管道，该管道将在命令启动时连接到命令的
// 标准输入。
// 等待命令退出后，管道将自动关闭。
// 调用者只需调用Close即可强制管道更快关闭。
// 例如，如果在标准输入
// 关闭之前，正在运行的命令不会退出，则调用者必须关闭管道。
func (c *Cmd) StdinPipe() (io.WriteCloser, error) {
	if c.Stdin != nil {
		return nil, errors.New("exec: Stdin already set")
	}
	if c.Process != nil {
		return nil, errors.New("exec: StdinPipe after process started")
	}
	pr, pw, err := os.Pipe()
	if err != nil {
		return nil, err
	}
	c.Stdin = pr
	c.closeAfterStart = append(c.closeAfterStart, pr)
	wc := &closeOnce{File: pw}
	c.closeAfterWait = append(c.closeAfterWait, wc)
	return wc, nil
}

type closeOnce struct {
	*os.File

	once sync.Once
	err  error
}

func (c *closeOnce) Close() error {
	c.once.Do(c.close)
	return c.err
}

func (c *closeOnce) close() {
	c.err = c.File.Close()
}

// StdoutPipe返回一个管道，该管道将在命令启动时连接到命令的
// 标准输出。
// 
// Wait将在看到命令退出后关闭管道，因此大多数调用者不需要自己关闭管道。因此，在管道的所有读取完成之前调用Wait 
// 是不正确的。
// 出于同样的原因，在使用StdoutPipe时调用Run是不正确的。
// 有关惯用用法，请参见示例。
func (c *Cmd) StdoutPipe() (io.ReadCloser, error) {
	if c.Stdout != nil {
		return nil, errors.New("exec: Stdout already set")
	}
	if c.Process != nil {
		return nil, errors.New("exec: StdoutPipe after process started")
	}
	pr, pw, err := os.Pipe()
	if err != nil {
		return nil, err
	}
	c.Stdout = pw
	c.closeAfterStart = append(c.closeAfterStart, pw)
	c.closeAfterWait = append(c.closeAfterWait, pr)
	return pr, nil
}

// StderrPipe返回一个管道，该管道将在命令启动时连接到命令的
// 标准错误。
// 
// Wait将在看到命令退出后关闭管道，因此大多数调用者不需要自己关闭管道。因此，在管道的所有读取完成之前调用Wait 
// 是不正确的。
// 出于同样的原因，在使用StderrPipe时使用Run是不正确的。
// 有关惯用用法，请参见StdoutPipe示例。
func (c *Cmd) StderrPipe() (io.ReadCloser, error) {
	if c.Stderr != nil {
		return nil, errors.New("exec: Stderr already set")
	}
	if c.Process != nil {
		return nil, errors.New("exec: StderrPipe after process started")
	}
	pr, pw, err := os.Pipe()
	if err != nil {
		return nil, err
	}
	c.Stderr = pw
	c.closeAfterStart = append(c.closeAfterStart, pw)
	c.closeAfterWait = append(c.closeAfterWait, pr)
	return pr, nil
}

// prefixSuffixSaver是一个io。Writer，它保留前N个字节
// 和最后N个字节。Bytes（）方法重建
// 它会显示一条非常错误的消息。
type prefixSuffixSaver struct {
	N         int // 前缀或后缀的最大大小
	prefix    []byte
	suffix    []byte // 一次环形缓冲区len（后缀）=N 
	suffixOff int    // 写入后缀
	skipped   int64

	// TODO的偏移量（bradfitz）：我们可以保留一个大的[]字节，并将其部分用于
	// 前缀，为“。。。省略N字节…'消息，
	// 然后是环形缓冲区后缀，在调用Bytes（）时，只需重新排列环形缓冲区
	// 后缀，但对于
	// 现在仅用于错误消息，似乎不值得。反正只有64KB。
}

func (w *prefixSuffixSaver) Write(p []byte) (n int, err error) {
	lenp := len(p)
	p = w.fill(&w.prefix, p)

	// 只保留后缀数据的最后w.N字节。
	if overage := len(p) - w.N; overage > 0 {
		p = p[overage:]
		w.skipped += int64(overage)
	}
	p = w.fill(&w.suffix, p)

	// w.如果p不为空，后缀现在已满。在圆圈中覆盖它。
	for len(p) > 0 { // 0、1或2次迭代。
		n := copy(w.suffix[w.suffixOff:], p)
		p = p[n:]
		w.skipped += int64(n)
		w.suffixOff += n
		if w.suffixOff == w.N {
			w.suffixOff = 0
		}
	}
	return lenp, nil
}

// fill将最多len（p）字节的p追加到*dst，这样*dst就不会比w.N大。它返回未追加的后缀p。
func (w *prefixSuffixSaver) fill(dst *[]byte, p []byte) (pRemain []byte) {
	if remain := w.N - len(*dst); remain > 0 {
		add := minInt(len(p), remain)
		*dst = append(*dst, p[:add]...)
		p = p[add:]
	}
	return p
}

func (w *prefixSuffixSaver) Bytes() []byte {
	if w.suffix == nil {
		return w.prefix
	}
	if w.skipped == 0 {
		return append(w.prefix, w.suffix...)
	}
	var buf bytes.Buffer
	buf.Grow(len(w.prefix) + len(w.suffix) + 50)
	buf.Write(w.prefix)
	buf.WriteString("\n... omitting ")
	buf.WriteString(strconv.FormatInt(w.skipped, 10))
	buf.WriteString(" bytes ...\n")
	buf.Write(w.suffix[w.suffixOff:])
	buf.Write(w.suffix[:w.suffixOff])
	return buf.Bytes()
}

func minInt(a, b int) int {
	if a < b {
		return a
	}
	return b
}

// DudupleEnv返回一个env的副本，其中删除了任何重复项，以支持
// 更高的值。
// 非正常环境“key=value”形式的项目保持不变。
func dedupEnv(env []string) []string {
	return dedupEnvCase(runtime.GOOS == "windows", env)
}

// dedupEnvCase是带有测试用例选项的dedupEnv。
// 如果不区分大小写为true，则忽略键的大小写。
func dedupEnvCase(caseInsensitive bool, env []string) []string {
	out := make([]string, 0, len(env))
	saw := make(map[string]int, len(env)) // key=>index into out 
	for _, kv := range env {
		k, _, ok := strings.Cut(kv, "=")
		if !ok {
			out = append(out, kv)
			continue
		}
		if caseInsensitive {
			k = strings.ToLower(k)
		}
		if dupIdx, isDup := saw[k]; isDup {
			out[dupIdx] = kv
			continue
		}
		saw[k] = len(out)
		out = append(out, kv)
	}
	return out
}

// /addCriticalEnv添加操作系统上需要的任何关键环境变量
// （或至少几乎总是需要的）。
// 目前这只用于Windows。我们已经有了。
func addCriticalEnv(env []string) []string {
	if runtime.GOOS != "windows" {
		return env
	}
	for _, kv := range env {
		k, _, ok := strings.Cut(kv, "=")
		if !ok {
			continue
		}
		if strings.EqualFold(k, "SYSTEMROOT") {
			return env
		}
	}
	return append(env, "SYSTEMROOT="+os.Getenv("SYSTEMROOT"))
}
